package io.github.cocoa.module.socket.config;

import com.corundumstudio.socketio.AckMode;
import com.corundumstudio.socketio.AuthorizationResult;
import com.corundumstudio.socketio.SocketConfig;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.SpringAnnotationScanner;
import com.corundumstudio.socketio.protocol.JacksonJsonSupport;
import com.corundumstudio.socketio.store.RedissonStoreFactory;
import com.corundumstudio.socketio.store.pubsub.PubSubStore;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.github.cocoa.framework.jackson.core.databind.module.JsLongModule;
import io.github.cocoa.module.socket.listener.NettySocketIoExceptionListener;
import io.netty.handler.codec.http.HttpHeaders;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

/**
 * netty socketIo 配置
 */
@Slf4j
@Configuration
public class NettySocketIoConfig {

    @Resource
    private RedissonClient redissonClient;
    @Resource
    private NettySocketIoProperty socketIoProperty;

    /**
     * netty socketIo 实例
     *
     * @return SocketIOServer
     */
    @Bean("socketIOServer")
    public SocketIOServer socketIOServer() {
        // socket 配置
        SocketConfig socketConfig = new SocketConfig();

        socketConfig.setTcpNoDelay(true);
        // 在默认情况下, 当调用 close 关闭 socket 的使用, close 会立即返回
        // 但是, 如果 send buffer 中还有数据, 系统会试着先把 send buffer 中的数据发送出去, 然后 close 才返回
        socketConfig.setSoLinger(0);
        // 设置复用地址
        socketConfig.setReuseAddress(true);
        com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
        // 设置监听端口
        config.setPort(socketIoProperty.getSocketPort());
        // 协议升级超时时间(毫秒), 默认10000. HTTP 握手升级为 ws 协议超时时间
        config.setUpgradeTimeout(socketIoProperty.getUpgradeTimeout());
        // Ping心跳间隔(毫秒), 默认25000. 客户端向服务器发送一条心跳消息间隔
        config.setPingInterval(socketIoProperty.getPingInterval());
        // Ping消息超时时间(毫秒), 默认60000. 这个时间间隔内没有接收到心跳消息就会发送超时事件
        config.setPingTimeout(socketIoProperty.getPingTimeout());
        config.setExceptionListener(new NettySocketIoExceptionListener());
        // 设置 JSON 支持
        config.setJsonSupport(new JacksonJsonSupport(
                // 解决 LocalDateTime 的序列化
                new JavaTimeModule(),
                // 解决 JS 出现精度丢失问题 的序列化
                new JsLongModule()
        ));
        // 配置跨域
        config.setOrigin("*");
        // 推荐使用redisson
        config.setStoreFactory(createRedissonStoreFactory());
        // 手动确认
        config.setAckMode(AckMode.AUTO_SUCCESS_ONLY);
        // 握手协议参数使用 JWT 的 Token 认证方案 认证方案
        config.setAuthorizationListener(data -> {
            HttpHeaders httpHeaders = data.getHttpHeaders();
            String token = httpHeaders.get("Authorization");
            String userAgent = httpHeaders.get("User-Agent");
            log.info("socketIOServer AuthorizationResult: {}", userAgent);
            // return AuthorizationResult.FAILED_AUTHORIZATION;
            return AuthorizationResult.SUCCESSFUL_AUTHORIZATION;
        });
        config.setSocketConfig(socketConfig);
        log.info("创建 SocketIOServer 结束");
        return new SocketIOServer(config);
    }

    /**
     * Spring 注解扫描器
     *
     * @param socketServer SocketIOServer
     * @return SpringAnnotationScanner
     */
    @Bean
    public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
        return new SpringAnnotationScanner(socketServer);
    }

    @Bean
    public PubSubStore pubSubStore(SocketIOServer socketServer) {
        return socketServer.getConfiguration().getStoreFactory().pubSubStore();
    }

    /**
     * 创建 StoreFactory
     *
     * @return RedissonStoreFactory
     */
    private RedissonStoreFactory createRedissonStoreFactory() {
        log.info("创建 RedissonStoreFactory 开始");
        RedissonStoreFactory redissonStoreFactory = new RedissonStoreFactory(redissonClient);
        log.info("创建 RedissonStoreFactory 结束");
        return redissonStoreFactory;
    }

}
